# -*- coding: utf-8 -*-
"""

"""
import os
import time
import traceback
import warnings
warnings.filterwarnings('ignore')

import numpy as np
import pandas as pd
from alphalib.config_product import order_path, data_center_path, data_path, utc_offset, glob_var
from alphalib.product.utils.commons import retry_wrapper
from alphalib.product.utils.dingding import send_wechat_work_msg
from alphalib.product.api.market import fetch_binance_ticker_data, reset_leverage
from alphalib.product.api.position import replenish_bnb, get_spot_position_and_equity, bn_transfer_spot_to_u, get_position_df, get_equity
from alphalib.product.api.trade import transfer_bnb


# =====策略相关函数
def del_insufficient_data(symbol_candle_data):
    """
    删除数据长度不足的币种信息

    :param symbol_candle_data:
    :return
    """
    # ===删除成交量为0的线数据、k线数不足的币种
    symbol_list = list(symbol_candle_data.keys())
    for symbol in symbol_list:
        # 删除空的数据
        if symbol_candle_data[symbol] is None or symbol_candle_data[symbol].empty:
            del symbol_candle_data[symbol]
            continue
        # 删除该币种成交量=0的k线
        symbol_candle_data[symbol] = symbol_candle_data[symbol][symbol_candle_data[symbol]['volume'] > 0]

    return symbol_candle_data


def transfer_swap(select_coin, df_swap, special_symbol_dict):
    """
    将现货中的数据替换成合约数据，主要替换：close
    :param select_coin:     选币数据
    :param df_swap:         合约数据
    :param special_symbol_dict: 特殊币种对应
    :return:
    """
    # 反转k,v的对应关系。现货对应合约，转换 ，合约对应现货
    special_symbol_dict = {v: k for k, v in special_symbol_dict.items()}
    # 整理币种名称，使合约和现货的币种名称相同
    df_swap['symbol_spot'] = df_swap['symbol'].apply(lambda x: special_symbol_dict.get(x.replace('USDT', ''), (x.split('1000')[1] if '1000' in x else x).replace('USDT', ''))+'USDT')
    # 设置指定字段
    df_swap = df_swap[['candle_begin_time', 'symbol', 'symbol_type', 'close', 'tag', 'symbol_spot']]

    # 将选币结果中是否含有合约的数据筛选出来
    not_swap_df = select_coin[select_coin['tag'] != 'HasSwap']
    has_swap_df = select_coin[select_coin['tag'] == 'HasSwap']  # 对含有合约的现货数据进行处理

    # 设置需要替换的字段
    other_cols = list(set(select_coin.columns) - set(df_swap.columns))
    has_swap_df = pd.merge(left=has_swap_df[['candle_begin_time', 'symbol'] + other_cols], right=df_swap,
                           left_on=['candle_begin_time', 'symbol'], right_on=['candle_begin_time', 'symbol_spot'],
                           how='left', suffixes=('', '_swap'))
    has_swap_df['symbol'] = has_swap_df['symbol_swap']
    del has_swap_df['symbol_swap'], has_swap_df['symbol_spot']

    # 将拆分的选币数据，合并回去
    select_coin = pd.concat([not_swap_df, has_swap_df], axis=0)
    select_coin.sort_values(['candle_begin_time', '方向'], inplace=True)
    select_coin.reset_index(inplace=True, drop=True)

    return select_coin


def get_swap_tag(df, special_symbol_dict, min_kline_num, is_day_period, hour_offset):
    """
    在数据中加入tag列，表明现货是否拥有对应的合约（主要用于历史数据判断，最新数据不用这么费劲儿）
    :param df:
    :param special_symbol_dict: 特殊现货对应合约关系
    :param min_kline_num: 最少上市多久
    :return:
    """
    swap_path = os.path.join(data_center_path, f'kline/swap/{hour_offset}')
    symbol = df.iloc[0]['symbol'].replace('USDT', '')  # 只保留币种名称，不含结算币种
    df['tag'] = 'NoSwap'
    num = 0 if is_day_period else 24
    if df.iloc[0]['symbol_type'] == 'spot':
        # 现货的资金费率为0
        df['fundingRate'] = 0
        # 拼接出该币种合约数据的路径
        _symbol = special_symbol_dict.get(symbol, symbol)  # 兼容币种别名的
        _swap_file = os.path.join(swap_path, _symbol + 'USDT.csv')
        _swap_file2 = os.path.join(swap_path, '1000' + _symbol +'USDT.csv')
        # 如果存在合约数据的，在合约的上时间范围内标记HasSwap
        if os.path.exists(_swap_file) or os.path.exists(_swap_file2):
            try:
                _swap_df = pd.read_csv(_swap_file, encoding='gbk', parse_dates=['candle_begin_time'])
            except:
                _swap_df = pd.read_csv(_swap_file2, encoding='gbk', parse_dates=['candle_begin_time'])
            cond1 = df['candle_begin_time'] > _swap_df.iloc[0]['candle_begin_time'] + pd.to_timedelta('1H') * (min_kline_num + num)
            cond2 = df['candle_begin_time'] <= _swap_df.iloc[-1]['candle_begin_time']
            df.loc[cond1 & cond2, 'tag'] = 'HasSwap'

        # 合约有改名的情况 ========================================
        if _symbol != symbol:  # 兼容币种改名。LUNA -> LUNA2。2022-5-12之前还是LUNA，2022-9-10改名为LUNA2
            _swap_file = os.path.join(swap_path, symbol + 'USDT.csv')
            _swap_file2 = os.path.join(swap_path, '1000' + symbol +'USDT.csv')
            # 如果存在合约数据的，在合约的上时间范围内标记HasSwap
            if os.path.exists(_swap_file) or os.path.exists(_swap_file2):
                try:
                    _swap_df = pd.read_csv(_swap_file, encoding='gbk', parse_dates=['candle_begin_time'])
                except:
                    _swap_df = pd.read_csv(_swap_file2, encoding='gbk', parse_dates=['candle_begin_time'])
                cond1 = df['candle_begin_time'] > _swap_df.iloc[0]['candle_begin_time'] + pd.to_timedelta('1H') * (min_kline_num + num)
                cond2 = df['candle_begin_time'] <= _swap_df.iloc[-1]['candle_begin_time']
                df.loc[cond1 & cond2, 'tag'] = 'HasSwap'
    else:
        df['tag'] = None

    return df


# ====== 计算仓位比重
def calc_alloc_ratio(stg, stg_conf, all_spot_df, all_swap_df, n_jobs=1):
    # 计算选币并输出为目标资金分配
    alloc_ratio = stg.calc_alloc_ratio(all_spot_df, all_swap_df, stg_conf.start_date, stg_conf.end_date, n_jobs=n_jobs)
    alloc_ratio_dict = {
        symbol: group.droplevel('symbol') for symbol, group in alloc_ratio.groupby(level='symbol')
    }
    
    all_df = dict(all_spot_df , **all_swap_df)
    all_df_info = {}
    for symbol, df in all_df.items():
        if symbol not in alloc_ratio_dict:
            continue
        info_dict = {
            'candle_begin_time': df['candle_begin_time'],
            'symbol': df['symbol'],
            'symbol_raw': df['symbol_raw'],
            'close': df['close'],
            'target_alloc_ratio': pd.Series(
                data=alloc_ratio_dict[symbol].reindex(pd.Index(df['candle_begin_time']), fill_value=0).values,
                index=df.index, copy=False
            )
        }

        df_info_single = pd.DataFrame(data=info_dict, copy=False)
        # 创建筛选条件：当周期或上一个周期的alloc_ratio不为0
        cond_trade = (df_info_single['target_alloc_ratio'] != 0) | (
                df_info_single['target_alloc_ratio'].shift(1, fill_value=0) != 0)
        df_info_single = df_info_single[cond_trade]
        all_df_info[symbol] = df_info_single

    df_info = pd.concat(all_df_info.values(), ignore_index=True, copy=False)
    # 仅保留最后一行，作为下单依据
    df_info = df_info[df_info['candle_begin_time']==df_info['candle_begin_time'].max()]
    df_info['symbol_type'] = df_info['symbol'].apply(lambda x: x.split('.')[1])
    df_info['symbol'] = df_info['symbol_raw']
    df_info.sort_values(['candle_begin_time', 'symbol'], inplace=True)
    return df_info


# ====== 计算实际下单量
def cal_order_amount(select_coin, account_info):
    """
    计算实际下单量

    :param select_coin:             选币结果
    :param account_info:            账户配置信息
    :return:

               当前持仓量   目标持仓量  目标下单份数   实际下单量 交易模式
    AUDIOUSDT         0.0 -2891.524948          -3.0 -2891.524948     建仓
    BANDUSDT        241.1     0.000000           NaN  -241.100000     清仓
    C98USDT        -583.0     0.000000           NaN   583.000000     清仓
    ENJUSDT           0.0  1335.871133           3.0  1335.871133     建仓
    WAVESUSDT        68.4     0.000000           NaN   -68.400000     清仓
    KAVAUSDT       -181.8     0.000000           NaN   181.800000     清仓

    """
    exchange = account_info['exchange']
    # 更新现货持仓数据
    if account_info['has_spot_strategy']:
        _, spot_position, _, _ = get_spot_position_and_equity(exchange)
        account_info['spot_position'] = spot_position
    else:
        spot_position = account_info['spot_position']
    # 更新合约持仓数据
    swap_position = get_position_df(exchange)
    account_info['swap_position'] = swap_position

    swap_position.reset_index(inplace=True)
    swap_position['symbol_type'] = 'swap'

    spot_position.reset_index(inplace=True)
    spot_position['symbol_type'] = 'spot'

    all_position = pd.concat([swap_position, spot_position], ignore_index=True)
    # ===计算目标持仓量
    # ** 由于现货的手续费是扣除在购买的现货中，所以这里下单的时候，对下单金额进行打折
    # ** vol * price * (1 + r) = 下单金额
    # ** vol = 下单金额 / (1 + r) / price
    # select_coin['每个策略单方向分配金额'] = (swap_equity + spot_equity) * leverage * select_coin['cap_weight'] / 2 / 1.001
    # select_coin['每个策略单方向选币数量'] = select_coin.groupby(['candle_begin_time', 'strategy', '方向'])['symbol'].transform('size')
    # select_coin['目标持仓量'] = select_coin['每个策略单方向分配金额'] / select_coin['每个策略单方向选币数量'] / select_coin['close'] * select_coin['方向']

    # ===创建symbol_order，用来记录要下单的币种的信息
    # =创建一个空的symbol_order，里面有select_coin（选中的币）、all_position（当前持仓）中的币种
    symbol_order = pd.concat([select_coin[['symbol', 'symbol_type']], all_position[['symbol', 'symbol_type']]], ignore_index=True)
    symbol_order.drop_duplicates(subset=['symbol', 'symbol_type'], inplace=True)
    symbol_order.set_index(['symbol', 'symbol_type'], inplace=True)
    all_position.set_index(['symbol', 'symbol_type'], inplace=True)
    # =symbol_order中更新当前持仓量
    symbol_order['当前持仓量'] = all_position['当前持仓量']
    symbol_order['当前持仓量'].fillna(value=0, inplace=True)

    # =目前持仓量当中，可能可以多空合并
    if select_coin.empty:
        symbol_order['目标持仓量'] = 0
    else:
        symbol_order['目标持仓量'] = select_coin.groupby(['symbol', 'symbol_type'])[['目标持仓量']].sum()
        symbol_order['目标持仓量'].fillna(value=0, inplace=True)

    # ===计算实际下单量和实际下单资金
    symbol_order['实际下单量'] = symbol_order['目标持仓量'] - symbol_order['当前持仓量']

    # ===计算下单的模式，清仓、建仓、调仓等
    symbol_order = symbol_order[symbol_order['实际下单量'] != 0]  # 过滤掉实际下当量为0的数据
    symbol_order.loc[symbol_order['目标持仓量'] == 0, '交易模式'] = '清仓'
    symbol_order.loc[symbol_order['当前持仓量'] == 0, '交易模式'] = '建仓'
    symbol_order['交易模式'].fillna(value='调仓', inplace=True)  # 增加或者减少原有的持仓，不会降为0

    if select_coin.empty:
        symbol_order['实际下单资金'] = np.nan
    else:
        select_coin.sort_values('candle_begin_time', inplace=True)
        symbol_order['close'] = select_coin.groupby(['symbol', 'symbol_type'])[['close']].last()
        symbol_order['实际下单资金'] = symbol_order['实际下单量'] * symbol_order['close']
        del symbol_order['close']
    symbol_order.reset_index(inplace=True)

    # 补全历史持仓的最新价格信息
    if symbol_order['实际下单资金'].isnull().any():
        symbol_swap_price = fetch_binance_ticker_data(exchange)  # 获取合约的最新价格
        symbol_spot_price = fetch_binance_ticker_data(exchange, symbol_type='spot')  # 获取现货的最新价格

        # 获取合约中实际下单资金为nan的数据
        swap_nan = symbol_order.loc[(symbol_order['实际下单资金'].isnull()) & (symbol_order['symbol_type'] == 'swap')]
        if not swap_nan.empty:
            # 补充一下合约中实际下单资金为nan的币种数据，方便后续进行拆单
            for _index in swap_nan.index:
                symbol_order.loc[_index, '实际下单资金'] = symbol_order.loc[_index, '实际下单量'] * symbol_swap_price[swap_nan.loc[_index, 'symbol']]

        # 获取现货中实际下单资金为nan的数据
        # 有些spot不存在价格，无法直接乘，eg：ethw
        spot_nan = symbol_order.loc[(symbol_order['实际下单资金'].isnull()) & (symbol_order['symbol_type'] == 'spot')]
        if not spot_nan.empty:
            has_price_spot = list(set(spot_nan['symbol'].to_list()) & set(symbol_spot_price.index))  # 筛选有USDT报价的现货
            spot_nan = spot_nan[spot_nan['symbol'].isin(has_price_spot)]  # 过滤掉没有USDT报价的现货，没有报价也表示卖不出去
            if not spot_nan.empty:  # 对含有报价的现货，补充 实际下单资金 数据
                # 补充一下现货中实际下单资金为nan的币种数据，方便后续进行拆单
                for _index in spot_nan.index:
                    symbol_order.loc[_index, '实际下单资金'] = symbol_order.loc[_index, '实际下单量'] * symbol_spot_price[spot_nan.loc[_index, 'symbol']]
            else:  # 对没有报价的现货，设置 实际下单资金 为1，进行容错
                symbol_order.loc[spot_nan.index, '实际下单资金'] = 1

    return symbol_order


def save_select_coin(select_coin, run_time, account_name, max_file_limit=999):
    """
    保存选币数据，最多保留999份文件
    :param select_coin: 保存文件内容
    :param run_time: 当前运行时间
    :param account_name: 账户名称
    :param max_file_limit: 最大限制
    """
    # 获取存储文件位置
    dir_path = os.path.join(data_path, account_name, 'select_coin')
    if not os.path.exists(dir_path):
        os.makedirs(dir_path)
    file_path = os.path.join(dir_path, f"{run_time.strftime('%Y-%m-%d_%H')}.pkl")
    # 保存文件
    select_coin.to_pickle(file_path)
    # 删除多余的文件
    del_file(dir_path, max_file_limit, file_suffix='.pkl')


def del_file(file_path, max_file_limit=999, file_suffix='.pkl'):
    """
    删除多余的文件，最限制max_file_limit
    :param file_path: 文件路径
    :param max_file_limit: 最大限制
    :param file_suffix: 文件后缀
    """
    # ===删除多余的flag文件
    files = [_ for _ in os.listdir(file_path) if _.endswith(file_suffix)]  # 获取file_path目录下所有以.pkl结尾的文件
    # 判断一下当前目录下文件是否过多
    if len(files) > max_file_limit:  # 文件数量超过最大文件数量限制，保留近999个文件，之前的文件全部删除
        print(f'目前文件数量: {len(files)}, 文件超过最大限制: {max_file_limit}，准备删除文件')
        files = sorted(files, reverse=True)  # 文件名称是时间命名的，所以这里倒序排序结果，距离今天时间越近的排在前面，距离距离今天时间越远的排在最后。例：[2023-04-02_08, 2023-04-02_07, 2023-04-02_06···]
        rm_files = files[max_file_limit:]  # 获取需要删除的文件列表

        # 遍历删除文件
        for _ in rm_files:
            os.remove(os.path.join(file_path, _))  # 删除文件
            print(os.path.join(file_path, _), '删除文件完成')


def calc_spot_need_usdt_amount(account_info, select_coin, spot_order):
    """
    计算现货账号需要划转多少usdt过去
    """
    # 获取需要计算的数据
    exchange = account_info['exchange']
    wechat_webhook_url = account_info['wechat_webhook_url']

    # 现货下单总资金
    spot_strategy_equity = 0 if select_coin.empty else spot_order[spot_order['实际下单资金'] > 0]['实际下单资金'].sum()

    # 计算现货下单总资金 与 当前现货的资金差值，需要补充（这里是多加2%的滑点）
    diff_equity = spot_strategy_equity * 1.02

    # 获取合约账户中可以划转的USDT数量
    swap_assets = retry_wrapper(exchange.fapiPrivateV2_get_account, params={'timestamp': ''}, func_name='获取U本位合约账户净值')  # 获取账户净值
    swap_assets = pd.DataFrame(swap_assets['assets'])
    swap_max_withdraw_amount = float(swap_assets[swap_assets['asset'] == 'USDT']['maxWithdrawAmount'])  # 获取可划转USDT数量
    swap_max_withdraw_amount = swap_max_withdraw_amount * 0.99  # 出于安全考虑，给合约账户预留1%的保证金

    # 计算可以划转的USDT数量
    transfer_amount = min(diff_equity, swap_max_withdraw_amount)
    # 现货需要的USDT比可划转金额要大，这里发送信息警告(前提：非纯多现货模式下)
    if diff_equity > swap_max_withdraw_amount:
        msg = '======警告======\n\n'
        msg += f'现货所需金额:{diff_equity:.2f}\n'
        msg += f'合约可划转金额:{swap_max_withdraw_amount:.2f}\n'
        msg += '划转资金不足，可能会造成现货下单失败！！！'
        # 重复发送五次
        for i in range(0, 5, 1):
            send_wechat_work_msg(msg, wechat_webhook_url)
            time.sleep(3)

    return transfer_amount


def create_finish_flag(flag_path, run_time, signal):
    """
    创建数据更新成功的标记文件
    如果标记文件过多，会删除7天之前的数据

    :param flag_path:标记文件存放的路径
    :param run_time: 当前的运行是时间
    :param signal: 信号
    """
    # ===判断数据是否完成
    if signal > 0:
        print('当前数据更新出现错误信号: ', signal, '，数据更新没有完成，当前小时不生成 flag 文件')
        return

    # ===生成flag文件
    # 指定生成文件名称
    index_config_path = os.path.join(flag_path, f"{run_time.strftime('%Y-%m-%d_%H_%M')}.flag")  # 例如文件名是：2023-04-02_08.flag
    # 更新信息成功，生成文件
    with open(index_config_path, 'w', encoding='utf-8') as f:
        f.write('更新完成')
        f.close()

    # ===删除多余的flag文件
    del_file(flag_path, 7 * 24, file_suffix='.flag')


def update_account_info(account_name, account_info, is_operate=True):
    """
    获取账户信息
    :param account_info:            config中配置跌账户
    :param is_only_spot_account:    是否只保留现货交易的账户
    :param is_operate:              是否进行账户的调整操作
    :return:
    """
    # 获取每个账号下的净值和持仓
    try:
        # ===加载合约和现货的数据
        # =获取U本位合约持仓
        swap_position = get_position_df(account_info['exchange'])
        # =获取U本位合约账户净值(包含未实现盈亏)
        swap_equity, _, _ = get_equity(account_info['exchange'])

        # ===加载现货交易对的信息
        # 判断是否使用现货实盘
        if account_info['has_spot_strategy']:  # 如果使用现货实盘，需要读取现货交易对信息和持仓信息
            # =获取现货持仓净值(包含实现盈亏，这是现货自带的)
            spot_equity, spot_position, spot_usdt, dust_spot = get_spot_position_and_equity(account_info['exchange'])
            if account_info['if_transfer_bnb']:
                # =小额资产转换
                transfer_bnb(account_info['exchange'], dust_spot)
        else:  # 不使用现货实盘，设置现货价值为默认值0，持仓为空
            spot_equity = 0
            spot_position = pd.DataFrame()
            spot_usdt = 0
        print('合约净值(不含浮动盈亏)：', swap_equity, '\t现货净值:', spot_equity, '\t现货的USDT：', spot_usdt)

        # 判断当前账号是否有资金
        if swap_equity + spot_equity <= 0:
            print(f'账户: {account_name}，资金不足，移除账户配置')
            # 返回空
            return None

        # 判断是否需要进行账户的调整（划转，买BNB，调整页面杠杆）
        if is_operate:
            # ===设置一下页面最大杠杆
            reset_leverage(account_info['exchange'], 5)

            # ===将现货中的U转到合约账户
            if spot_usdt > account_info['buy_bnb_value'] * 2:
                bn_transfer_spot_to_u(account_info['exchange'], round(spot_usdt - account_info['buy_bnb_value'] * 2, 1))
                spot_equity -= round(spot_usdt - account_info['buy_bnb_value'] * 2, 1)  # 现货总净值扣除掉划走USDT
                swap_equity += round(spot_usdt - account_info['buy_bnb_value'] * 2, 1)  # 合约总净值加上掉划走USDT

            # ===补充BNB操作(用于抵扣手续费)
            if account_info['if_use_bnb_burn']:  # 判断是否开启BNB燃烧
                # 合约资金有钱补充BNB
                if swap_equity > account_info['buy_bnb_value']:
                    all_buy_bnb_value = replenish_bnb(account_info['exchange'], account_info['buy_bnb_value'], account_info['has_spot_strategy'])
                    if all_buy_bnb_value > 0:
                        # 发送信息给机器人
                        send_wechat_work_msg(f'补充BNB抵扣手续费操作完成，补充金额：{all_buy_bnb_value:.4f} U', account_info['wechat_webhook_url'])

        # ===更新账户信息
        account_info['swap_position'] = swap_position
        account_info['swap_equity'] = swap_equity
        account_info['spot_position'] = spot_position
        account_info['spot_equity'] = spot_equity - account_info['buy_bnb_value'] * 2  # 保留bnb手续费抵扣资金

        # ===休息一下
        time.sleep(1)
    except KeyboardInterrupt:
        print('退出')
        exit()
    except BaseException as e:
        msg = f'账户: {account_name}，更新账户信息出现问题，移除账户配置'
        print(msg)
        print(traceback.format_exc())
        send_wechat_work_msg(msg, account_info['wechat_webhook_url'])
        # 发生错误，移除当前账户
        return None

    return account_info


def refresh_diff_time(exchange):
    """
    刷新本地电脑与交易所的时差
    :param exchange:    交易所对象
    """
    server_time = exchange.fetch_time()  # 获取交易所时间
    diff_timestamp = int(time.time() * 1000) - server_time  # 计算时差
    glob_var.update_diff_time(diff_timestamp)  # 更新到全局变量中


def save_and_merge_select(select_coin, account_name, account_info, auto_reb = True):
    if select_coin.empty:
        return select_coin

    # 解析账户信息
    leverage = account_info['leverage']
    exchange = account_info['exchange']
    buy_bnb_value = account_info['buy_bnb_value']
    swap_position = account_info['swap_position']
    # 更新一下最新的现货数据
    spot_equity = get_spot_position_and_equity(exchange)[0]
    swap_equity, swap_margin, _ = get_equity(exchange)
    account_info['swap_equity'] = swap_equity
    account_info['spot_equity'] = spot_equity

    # 构建本次存放选币下单的文件
    order_file = os.path.join(order_path, f'{account_name}_order.csv')

    # 杠杆为0，表示清仓
    if leverage == 0:
        select_coin.drop(select_coin.index, inplace=True)
        # 清仓之后删除本地文件
        if os.path.exists(order_file):
            os.remove(order_file)
        return select_coin

    # ==计算目标持仓量
    select_coin['方向'] = 0
    select_coin.loc[select_coin['target_alloc_ratio'] > 0, '方向'] = 1
    select_coin.loc[select_coin['target_alloc_ratio'] < 0, '方向'] = -1

    # 获取当前账户总资金
    if auto_reb:
        # 滚仓需要包含未实现盈亏
        all_equity = swap_equity + spot_equity
    else:
        # 不滚仓不需要包含未实现盈亏
        all_equity = swap_equity + swap_margin
    # 判断是否是纯多头交易
    if (len(select_coin['方向'].unique()) == 1) and (1 in select_coin['方向']):
        # 如果手续费占总资金的比例 超过 千1，动态调整一下杠杆
        if buy_bnb_value / (swap_equity + spot_equity) >= 0.001:
            # 如果当前选币为纯多，自动降低下单杠杆5%，防止现货下单滑点的影响（因为现货杠杆不能超过1，所以直接设置0.95）
            all_equity = all_equity * min(0.95, 0.95 * leverage)
        else:  # 如果手续费占总资金的比例 小于 千1，减去300
            all_equity = all_equity * min(1, leverage) - 300
    else:  # 其他交易状态
        all_equity = all_equity * leverage

    select_coin['单币下单金额'] = all_equity * select_coin['target_alloc_ratio'] 
    # select_coin['目标持仓量'] = select_coin['单币下单金额'] / select_coin['close']
    # 获取最新价格
    swap_price = fetch_binance_ticker_data(exchange, symbol_type='swap')
    spot_price = fetch_binance_ticker_data(exchange, symbol_type='spot')
    select_coin.dropna(subset=['symbol'], inplace=True)
    select_coin['最新价格'] = select_coin.apply(lambda row: swap_price[row['symbol']] if row['symbol_type'] == 'swap' else spot_price[row['symbol']], axis=1)
    select_coin['目标持仓量'] = select_coin['单币下单金额'] / select_coin['最新价格']

    # 设置指定字段保留
    cols = [
        'candle_begin_time', 'symbol', 'symbol_type', 'close', '方向', 'target_alloc_ratio', '单币下单金额', '目标持仓量', '最新价格',
    ]
    select_coin = select_coin[cols]
    
    # 严格对照回测，不考虑if_rebalance了
    return select_coin


def save_symbol_order(symbol_order, run_time, account_name):
    # 创建存储账户换仓信息文件的目录[为了计算账户小时成交量信息生成的]
    dir_path = os.path.join(data_path, account_name, '账户换仓信息')
    if not os.path.exists(dir_path):
        os.makedirs(dir_path)
    filename = run_time.strftime("%Y%m%d_%H") + ".csv"
    select_symbol_list_path = os.path.join(dir_path, filename)
    select_symbol_list = symbol_order[['symbol', 'symbol_type']].copy()
    select_symbol_list['time'] = run_time
    select_symbol_list.to_csv(select_symbol_list_path)
    del_file(dir_path, 999, file_suffix='.csv')
